1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 *   Licensed to the Apache Software Foundation (ASF) under one or more
12 *   contributor license agreements. See the NOTICE file distributed
13 *   with this work for additional information regarding copyright
14 *   ownership. The ASF licenses this file to you under the Apache
15 *   License, Version 2.0 (the "License"); you may not use this file
16 *   except in compliance with the License. You may obtain a copy of
17 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20
21#include <osx/salinst.h>
22#include <quartz/utils.h>
23#include <quartz/salgdi.h>
24
25#include "a11ytextattributeswrapper.h"
26
27#include <com/sun/star/accessibility/AccessibleTextType.hpp>
28#include <com/sun/star/awt/FontUnderline.hpp>
29#include <com/sun/star/awt/FontWeight.hpp>
30#include <com/sun/star/awt/FontStrikeout.hpp>
31#include <com/sun/star/lang/IllegalArgumentException.hpp>
32#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
33#include <com/sun/star/text/TextMarkupType.hpp>
34#include <com/sun/star/style/ParagraphAdjust.hpp>
35
36namespace css_awt = ::com::sun::star::awt;
37using namespace ::com::sun::star::accessibility;
38using namespace ::com::sun::star::beans;
39using namespace ::com::sun::star::lang;
40using namespace ::com::sun::star::uno;
41
42// cannot use NSFontDescriptor as it has no notion of explicit NSUn{bold,italic}FontMask
43@interface AquaA11yFontDescriptor : NSObject
44{
45    NSString *_name;
46    NSFontTraitMask _traits;
47    CGFloat _size;
48}
49-(void)setName:(NSString*)name;
50-(void)setBold:(NSFontTraitMask)bold;
51-(void)setItalic:(NSFontTraitMask)italic;
52-(void)setSize:(CGFloat)size;
53-(NSFont*)font;
54@end
55
56@implementation AquaA11yFontDescriptor
57- (id)init
58{
59    if((self = [super init]))
60    {
61        _name = nil;
62        _traits = 0;
63        _size = 0.0;
64    }
65    return self;
66}
67
68- (id)initWithDescriptor:(AquaA11yFontDescriptor*)descriptor {
69    if((self = [super init]))
70    {
71        _name = [descriptor->_name retain];
72        _traits = descriptor->_traits;
73        _size = descriptor->_size;
74    }
75    return self;
76}
77
78- (void)dealloc {
79    [_name release];
80    [super dealloc];
81}
82
83-(void)setName:(NSString*)name {
84    if (_name != name) {
85        [name retain];
86        [_name release];
87        _name = name;
88    }
89}
90
91-(void)setBold:(NSFontTraitMask)bold {
92    _traits &= ~(NSBoldFontMask | NSUnboldFontMask);
93    _traits |= bold & (NSBoldFontMask | NSUnboldFontMask);
94};
95
96-(void)setItalic:(NSFontTraitMask)italic {
97    _traits &= ~(NSItalicFontMask | NSUnitalicFontMask);
98    _traits |= italic & (NSItalicFontMask | NSUnitalicFontMask);
99};
100
101-(void)setSize:(CGFloat)size { _size = size; }
102
103-(NSFont*)font {
104    return [[NSFontManager sharedFontManager] fontWithFamily:_name traits:_traits weight:0 size:_size];
105}
106@end
107
108@implementation AquaA11yTextAttributesWrapper : NSObject
109
110+(int)convertUnderlineStyle:(PropertyValue)property {
111    int underlineStyle = NSUnderlineStyleNone;
112    sal_Int16 value = 0;
113    property.Value >>= value;
114    if ( value != ::css_awt::FontUnderline::NONE
115      && value != ::css_awt::FontUnderline::DONTKNOW) {
116        underlineStyle = NSUnderlineStyleSingle;
117    }
118    return underlineStyle;
119}
120
121+(int)convertBoldStyle:(PropertyValue)property {
122    int boldStyle = NSUnboldFontMask;
123    float value = 0;
124    property.Value >>= value;
125    if ( value == ::css_awt::FontWeight::SEMIBOLD
126      || value == ::css_awt::FontWeight::BOLD
127      || value == ::css_awt::FontWeight::ULTRABOLD
128      || value == ::css_awt::FontWeight::BLACK ) {
129        boldStyle = NSBoldFontMask;
130    }
131    return boldStyle;
132}
133
134+(int)convertItalicStyle:(PropertyValue)property {
135    int italicStyle = NSUnitalicFontMask;
136    ::css_awt::FontSlant value = property.Value.get< ::css_awt::FontSlant>();
137    if ( value == ::css_awt::FontSlant_ITALIC ) {
138        italicStyle = NSItalicFontMask;
139    }
140    return italicStyle;
141}
142
143+(BOOL)isStrikethrough:(PropertyValue)property {
144    BOOL strikethrough = NO;
145    sal_Int16 value = 0;
146    property.Value >>= value;
147    if ( value != ::css_awt::FontStrikeout::NONE
148      && value != ::css_awt::FontStrikeout::DONTKNOW ) {
149        strikethrough = YES;
150    }
151    return strikethrough;
152}
153
154+(BOOL)convertBoolean:(PropertyValue)property {
155    BOOL myBoolean = NO;
156    bool value = false;
157    property.Value >>= value;
158    if ( value ) {
159        myBoolean = YES;
160    }
161    return myBoolean;
162}
163
164+(NSNumber *)convertShort:(PropertyValue)property {
165    sal_Int16 value = 0;
166    property.Value >>= value;
167    return [ NSNumber numberWithShort: value ];
168}
169
170+(void)addColor:(Color)nColor forAttribute:(NSString *)attribute andRange:(NSRange)range toString:(NSMutableAttributedString *)string {
171    if( nColor == COL_TRANSPARENT )
172        return;
173    const RGBAColor aRGBAColor( nColor);
174    CGColorRef aColorRef = CGColorCreate ( CGColorSpaceCreateWithName ( kCGColorSpaceGenericRGB ), aRGBAColor.AsArray() );
175    [ string addAttribute: attribute value: reinterpret_cast<id>(aColorRef) range: range ];
176    CGColorRelease( aColorRef );
177}
178
179+(void)addFont:(NSFont *)font toString:(NSMutableAttributedString *)string forRange:(NSRange)range {
180    if ( font != nil ) {
181        NSDictionary * fontDictionary = [ NSDictionary dictionaryWithObjectsAndKeys:
182            [ font fontName ], NSAccessibilityFontNameKey,
183            [ font familyName ], NSAccessibilityFontFamilyKey,
184            [ font displayName ], NSAccessibilityVisibleNameKey,
185            [ NSNumber numberWithFloat: [ font pointSize ] ], NSAccessibilityFontSizeKey,
186            nil
187        ];
188        [ string addAttribute: NSAccessibilityFontTextAttribute
189                value: fontDictionary
190                range: range
191        ];
192    }
193}
194
195+(void)applyAttributesFrom:(Sequence < PropertyValue >)attributes toString:(NSMutableAttributedString *)string forRange:(NSRange)range fontDescriptor:(AquaA11yFontDescriptor*)fontDescriptor {
196    NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
197    // constants
198    static const OUString attrUnderline("CharUnderline");
199    static const OUString attrBold("CharWeight");
200    static const OUString attrFontname("CharFontName");
201    static const OUString attrItalic("CharPosture");
202    static const OUString attrHeight("CharHeight");
203    static const OUString attrStrikethrough("CharStrikeout");
204    static const OUString attrShadow("CharShadowed");
205    static const OUString attrUnderlineColor("CharUnderlineColor");
206    static const OUString attrUnderlineHasColor("CharUnderlineHasColor");
207    static const OUString attrForegroundColor("CharColor");
208    static const OUString attrBackgroundColor("CharBackColor");
209    static const OUString attrSuperscript("CharEscapement");
210    static const OUString attrTextAlignment("ParaAdjust");
211    // vars
212    sal_Int32 underlineColor = 0;
213    BOOL underlineHasColor = NO;
214    // add attributes to string
215    for ( int attrIndex = 0; attrIndex < attributes.getLength(); attrIndex++ ) {
216        PropertyValue property = attributes [ attrIndex ];
217        // TODO: NSAccessibilityMisspelledTextAttribute, NSAccessibilityAttachmentTextAttribute, NSAccessibilityLinkTextAttribute
218        // NSAccessibilityStrikethroughColorTextAttribute is unsupported by UNP-API
219        if ( property.Value.hasValue() ) {
220            if ( property.Name.equals ( attrUnderline ) ) {
221                int style = [ AquaA11yTextAttributesWrapper convertUnderlineStyle: property ];
222                if ( style != NSUnderlineStyleNone ) {
223                    [ string addAttribute: NSAccessibilityUnderlineTextAttribute value: [ NSNumber numberWithInt: style ] range: range ];
224                }
225            } else if ( property.Name.equals ( attrFontname ) ) {
226                OUString fontname;
227                property.Value >>= fontname;
228                [fontDescriptor setName:CreateNSString(fontname)];
229            } else if ( property.Name.equals ( attrBold ) ) {
230                [fontDescriptor setBold:[AquaA11yTextAttributesWrapper convertBoldStyle:property]];
231            } else if ( property.Name.equals ( attrItalic ) ) {
232                [fontDescriptor setItalic:[AquaA11yTextAttributesWrapper convertItalicStyle:property]];
233            } else if ( property.Name.equals ( attrHeight ) ) {
234                float size;
235                property.Value >>= size;
236                [fontDescriptor setSize:size];
237            } else if ( property.Name.equals ( attrStrikethrough ) ) {
238                if ( [ AquaA11yTextAttributesWrapper isStrikethrough: property ] ) {
239                    [ string addAttribute: NSAccessibilityStrikethroughTextAttribute value: [ NSNumber numberWithBool: YES ] range: range ];
240                }
241            } else if ( property.Name.equals ( attrShadow ) ) {
242                if ( [ AquaA11yTextAttributesWrapper convertBoolean: property ] ) {
243                    [ string addAttribute: NSAccessibilityShadowTextAttribute value: [ NSNumber numberWithBool: YES ] range: range ];
244                }
245            } else if ( property.Name.equals ( attrUnderlineColor ) ) {
246                property.Value >>= underlineColor;
247            } else if ( property.Name.equals ( attrUnderlineHasColor ) ) {
248                underlineHasColor = [ AquaA11yTextAttributesWrapper convertBoolean: property ];
249            } else if ( property.Name.equals ( attrForegroundColor ) ) {
250                [ AquaA11yTextAttributesWrapper addColor: property.Value.get<sal_Int32>() forAttribute: NSAccessibilityForegroundColorTextAttribute andRange: range toString: string ];
251            } else if ( property.Name.equals ( attrBackgroundColor ) ) {
252                [ AquaA11yTextAttributesWrapper addColor: property.Value.get<sal_Int32>() forAttribute: NSAccessibilityBackgroundColorTextAttribute andRange: range toString: string ];
253            } else if ( property.Name.equals ( attrSuperscript ) ) {
254                // values < zero mean subscript
255                // values > zero mean superscript
256                // this is true for both NSAccessibility-API and UNO-API
257                NSNumber * number = [ AquaA11yTextAttributesWrapper convertShort: property ];
258                if ( [ number shortValue ] != 0 ) {
259                    [ string addAttribute: NSAccessibilitySuperscriptTextAttribute value: number range: range ];
260                }
261            } else if ( property.Name.equals ( attrTextAlignment ) ) {
262                sal_Int32 alignment;
263                property.Value >>= alignment;
264                NSNumber *textAlignment = nil;
265SAL_WNODEPRECATED_DECLARATIONS_PUSH
266    // 'NSCenterTextAlignment' is deprecated: first deprecated in macOS 10.12
267    // 'NSJustifiedTextAlignment' is deprecated: first deprecated in macOS 10.12
268    // 'NSLeftTextAlignment' is deprecated: first deprecated in macOS 10.12
269    // 'NSRightTextAlignment' is deprecated: first deprecated in macOS 10.12
270                switch(static_cast<css::style::ParagraphAdjust>(alignment)) {
271                    case css::style::ParagraphAdjust_RIGHT : textAlignment = [NSNumber numberWithInteger:NSRightTextAlignment]    ; break;
272                    case css::style::ParagraphAdjust_CENTER: textAlignment = [NSNumber numberWithInteger:NSCenterTextAlignment]   ; break;
273                    case css::style::ParagraphAdjust_BLOCK : textAlignment = [NSNumber numberWithInteger:NSJustifiedTextAlignment]; break;
274                    case css::style::ParagraphAdjust_LEFT  :
275                    default                                             : textAlignment = [NSNumber numberWithInteger:NSLeftTextAlignment]     ; break;
276                }
277SAL_WNODEPRECATED_DECLARATIONS_POP
278                NSDictionary *paragraphStyle = [NSDictionary dictionaryWithObjectsAndKeys:textAlignment, @"AXTextAlignment", textAlignment, @"AXVisualTextAlignment", nil];
279                [string addAttribute:@"AXParagraphStyle" value:paragraphStyle range:range];
280            }
281        }
282    }
283    // add underline information
284    if ( underlineHasColor ) {
285        [ AquaA11yTextAttributesWrapper addColor: underlineColor forAttribute: NSAccessibilityUnderlineColorTextAttribute andRange: range toString: string ];
286    }
287    // add font information
288    NSFont * font = [fontDescriptor font];
289    [AquaA11yTextAttributesWrapper addFont:font toString:string forRange:range];
290    [ pool release ];
291}
292
293+(void)addMarkup:(XAccessibleTextMarkup*)markup withType:(long)type toString:(NSMutableAttributedString*)string inRange:(NSRange)range {
294    const long markupCount = markup->getTextMarkupCount(type);
295    for (long markupIndex = 0; markupIndex < markupCount; ++markupIndex) {
296        TextSegment markupSegment = markup->getTextMarkup(markupIndex, type);
297        NSRange markupRange = NSMakeRange(markupSegment.SegmentStart, markupSegment.SegmentEnd - markupSegment.SegmentStart);
298        markupRange = NSIntersectionRange(range, markupRange);
299        if (markupRange.length > 0) {
300            markupRange.location -= range.location;
301            switch(type) {
302                case css::text::TextMarkupType::SPELLCHECK: {
303                    [string addAttribute:NSAccessibilityMisspelledTextAttribute value:[NSNumber numberWithBool:YES] range:markupRange];
304                    [string addAttribute:@"AXMarkedMisspelled" value:[NSNumber numberWithBool:YES] range:markupRange];
305                    break;
306                }
307            }
308        }
309    }
310}
311
312+(void)addMarkup:(XAccessibleTextMarkup*)markup toString:(NSMutableAttributedString*)string inRange:(NSRange)range {
313    [AquaA11yTextAttributesWrapper addMarkup:markup withType:css::text::TextMarkupType::SPELLCHECK toString:string inRange:range];
314}
315
316+(NSMutableAttributedString *)createAttributedStringForElement:(AquaA11yWrapper *)wrapper inOrigRange:(id)origRange {
317    static const Sequence < OUString > emptySequence;
318    // vars
319    NSMutableAttributedString * string = nil;
320    int loc = [ origRange rangeValue ].location;
321    int len = [ origRange rangeValue ].length;
322    int endIndex = loc + len;
323    int currentIndex = loc;
324    try {
325        NSString * myString = CreateNSString ( [ wrapper accessibleText ] -> getText() ); // TODO: dirty fix for i87817
326        string = [ [ NSMutableAttributedString alloc ] initWithString: CreateNSString ( [ wrapper accessibleText ] -> getTextRange ( loc, loc + len ) ) ];
327        if ( [ wrapper accessibleTextAttributes ] && [myString characterAtIndex:0] != 57361) { // TODO: dirty fix for i87817
328            [ string beginEditing ];
329            // add default attributes for whole string
330            Sequence < PropertyValue > defaultAttributes = [ wrapper accessibleTextAttributes ] -> getDefaultAttributes ( emptySequence );
331            AquaA11yFontDescriptor *defaultFontDescriptor = [[AquaA11yFontDescriptor alloc] init];
332            [ AquaA11yTextAttributesWrapper applyAttributesFrom: defaultAttributes toString: string forRange: NSMakeRange ( 0, len ) fontDescriptor: defaultFontDescriptor ];
333            // add attributes for attribute run(s)
334            while ( currentIndex < endIndex ) {
335                TextSegment textSegment = [ wrapper accessibleText ] -> getTextAtIndex ( currentIndex, AccessibleTextType::ATTRIBUTE_RUN );
336                int endOfRange = endIndex > textSegment.SegmentEnd ? textSegment.SegmentEnd : endIndex;
337                NSRange rangeForAttributeRun = NSMakeRange ( currentIndex - loc , endOfRange - currentIndex );
338                // add run attributes
339                Sequence < PropertyValue > attributes = [ wrapper accessibleTextAttributes ] -> getRunAttributes ( currentIndex, emptySequence );
340                AquaA11yFontDescriptor *fontDescriptor = [[AquaA11yFontDescriptor alloc] initWithDescriptor:defaultFontDescriptor];
341                [ AquaA11yTextAttributesWrapper applyAttributesFrom: attributes toString: string forRange: rangeForAttributeRun fontDescriptor: fontDescriptor ];
342                [fontDescriptor release];
343                currentIndex = textSegment.SegmentEnd;
344            }
345            [defaultFontDescriptor release];
346            if ([wrapper accessibleTextMarkup])
347                [AquaA11yTextAttributesWrapper addMarkup:[wrapper accessibleTextMarkup] toString:string inRange:[origRange rangeValue]];
348            [ string endEditing ];
349        }
350    } catch ( IllegalArgumentException & ) {
351        // empty
352    } catch ( IndexOutOfBoundsException & ) {
353        // empty
354    } catch ( RuntimeException& ) {
355        // at least don't crash
356    }
357    return string;
358}
359
360@end
361
362/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
363