/* * Copyright 2013-2015 Skynav, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY SKYNAV, INC. AND ITS CONTRIBUTORS “AS IS” AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL SKYNAV, INC. OR ITS CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.skynav.ttv.verifier.util; import java.util.List; import java.util.Set; import org.xml.sax.Locator; import com.skynav.ttv.model.value.FontFamily; import com.skynav.ttv.model.value.FontVariant; import com.skynav.ttv.model.value.GenericFontFamily; import com.skynav.ttv.model.value.impl.FontFamilyImpl; import com.skynav.ttv.util.Location; import com.skynav.ttv.util.Message; import com.skynav.ttv.util.Reporter; import com.skynav.ttv.verifier.VerifierContext; public class Fonts { public static boolean isFontFamily(String value, Location location, VerifierContext context, Object[] treatments, FontFamily[] outputFamily) { if (isGenericFontFamily(value, location, context, treatments, outputFamily)) return true; else if (isUnquotedFontFamily(value, location, context, treatments, outputFamily)) return true; else if (isQuotedFontFamily(value, location, context, treatments, outputFamily)) return true; else return false; } private static boolean isGenericFontFamily(String value, Location location, VerifierContext context, Object[] treatments, FontFamily[] outputFamily) { String trimmedValue = value.trim(); FontFamily family = FontFamilyImpl.getGenericFamily(trimmedValue); if (family != null) { if (outputFamily != null) outputFamily[0] = family; return true; } else return false; } private static boolean isUnquotedFontFamily(String value, Location location, VerifierContext context, Object[] treatments, FontFamily[] outputFamily) { String trimmedValue = value.trim(); if (trimmedValue.length() == 0) return false; else { List<String> identifiers = new java.util.ArrayList<String>(); if (Identifiers.isIdentifiers(trimmedValue, location, context, identifiers)) { if (outputFamily != null) outputFamily[0] = new FontFamilyImpl(FontFamily.Type.Unquoted, Identifiers.joinIdentifiersUnescaping(identifiers)); return true; } else return false; } } private static boolean isQuotedFontFamily(String value, Location location, VerifierContext context, Object[] treatments, FontFamily[] outputFamily) { String trimmedValue = value.trim(); if (trimmedValue.length() == 0) return false; else { Reporter reporter = context.getReporter(); String[] string = new String[1]; if (Strings.isDoubleQuotedString(trimmedValue, location, context, string) || Strings.isSingleQuotedString(trimmedValue, location, context, string)) { String stringContent = Strings.unescapeUnquoted(string[0]); if (treatments != null) { if (GenericFontFamily.isToken(stringContent)) { QuotedGenericFontFamilyTreatment quotedGenericTreatment = (QuotedGenericFontFamilyTreatment) treatments[0]; Message message = reporter.message(location.getLocator(), "*KEY*", "Quoted <familyName> expression is a generic font family, but will be treated as a non-generic family name."); if (quotedGenericTreatment == QuotedGenericFontFamilyTreatment.Warning) { if (reporter.logWarning(message)) { treatments[0] = QuotedGenericFontFamilyTreatment.Allow; // suppress second warning return false; } } else if (quotedGenericTreatment == QuotedGenericFontFamilyTreatment.Info) { reporter.logInfo(message); } } } if (outputFamily != null) outputFamily[0] = new FontFamilyImpl(FontFamily.Type.Quoted, stringContent); return true; } else return false; } } public static void badFontFamily(String value, Location location, VerifierContext context, Object[] treatments) { String trimmedValue = value.trim(); if (trimmedValue.length() == 0) { Reporter reporter = context.getReporter(); reporter.logInfo(reporter.message(location.getLocator(), "*KEY*", "Bad <familyName> of <genericFamilyName> expression, value is empty or only XML space characters.")); } else { char c = trimmedValue.charAt(0); if ((c != '\"') && (c != '\'')) badUnquotedFontFamily(trimmedValue, location, context, treatments); else badQuotedFontFamily(trimmedValue, location, context, treatments); } } public static void badUnquotedFontFamily(String value, Location location, VerifierContext context, Object[] treatments) { assert value.length() > 0; Identifiers.badIdentifiers(value, location, context); } public static void badQuotedFontFamily(String value, Location location, VerifierContext context, Object[] treatments) { Reporter reporter = context.getReporter(); Locator locator = location.getLocator(); assert value.length() > 0; char quote = value.charAt(0); int valueIndex = 1; int valueLength = value.length(); char c = 0; while (valueIndex < valueLength) { c = value.charAt(valueIndex++); if (c == '\\') { if (valueIndex == valueLength) { reporter.logInfo(reporter.message(locator, "*KEY*", "Bad quoted string in <familyName> expression, incomplete escape.")); return; } else ++valueIndex; } else if (c == quote) break; } if (c != quote) { reporter.logInfo(reporter.message(locator, "*KEY*", "Bad quoted string in <familyName> expression, not terminated.")); } else if (valueIndex < valueLength) { reporter.logInfo(reporter.message(locator, "*KEY*", "Bad quoted string in <familyName> expression, unrecognized characters following string, got ''{0}''.", value.substring(valueIndex))); } else if (valueLength < 3) { reporter.logInfo(reporter.message(locator, "*KEY*", "Bad quoted string in <familyName> expression, empty string.")); } } public static boolean isFontFamilies(String value, Location location, VerifierContext context, Object[] treatments, List<FontFamily> outputFamilies) { List<FontFamily> families = new java.util.ArrayList<FontFamily>(); String [] familyItems = splitFontFamilies(value); for (String item : familyItems) { FontFamily[] family = new FontFamily[1]; if (isFontFamily(item, location, context, treatments, family)) families.add(family[0]); else return false; } if (outputFamilies != null) { outputFamilies.clear(); outputFamilies.addAll(families); } return true; } public static void badFontFamilies(String value, Location location, VerifierContext context, Object[] treatments) { String [] familyItems = splitFontFamilies(value); Object[] treatmentsInner = (treatments != null) ? new Object[] { treatments[0] } : null; for (String item : familyItems) { if (!isFontFamily(item, location, context, treatmentsInner, null)) badFontFamily(item, location, context, treatmentsInner); } } private static String[] splitFontFamilies(String value) { List<String> items = new java.util.ArrayList<String>(); int valueIndex = 0; int valueLength = value.length(); int itemStart = valueIndex; while (true) { char c = 0; char quote = 0; while (valueIndex < valueLength) { c = value.charAt(valueIndex++); if (c == '\\') { if (valueIndex == valueLength) break; else valueIndex++; } else if ((c == '\'') || (c == '\"')) { if (quote == 0) quote = c; else if (c == quote) quote = 0; } else if ((c == ',') && (quote == 0)) { break; } } if (valueIndex > itemStart) items.add(value.substring(itemStart, c == ',' ? valueIndex - 1 : valueIndex)); itemStart = valueIndex; if (valueIndex >= valueLength) { if (c == ',') items.add(""); break; } } return items.toArray(new String[items.size()]); } public static boolean isFontVariant(String value, Location location, VerifierContext context, FontVariant[] outputVariant) { String trimmedValue = value.trim(); if (trimmedValue.length() == 0) return false; else { try { FontVariant fv = FontVariant.fromValue(trimmedValue); if (outputVariant != null) outputVariant[0] = fv; return true; } catch (IllegalArgumentException e) { return false; } } } public static void badFontVariant(String value, Location location, VerifierContext context) { String trimmedValue = value.trim(); Reporter reporter = context.getReporter(); Locator locator = location.getLocator(); if (trimmedValue.length() == 0) { reporter.logInfo(reporter.message(locator, "*KEY*", "Bad token of <font-variant> expression, value is empty or only XML space characters.")); } else { try { FontVariant.fromValue(trimmedValue); } catch (IllegalArgumentException e) { reporter.logInfo(reporter.message(locator, "*KEY*", "Bad token of <font-variant> expression, unknown token ''{0}''.", trimmedValue)); } } } public static boolean isFontVariants(String value, Location location, VerifierContext context, Set<FontVariant> outputVariants) { Set<FontVariant> variants = new java.util.HashSet<FontVariant>(); String [] variantTokens = splitFontVariants(value); for (String token : variantTokens) { FontVariant[] variant = new FontVariant[1]; if (isFontVariant(token, location, context, variant)) { if (variants.contains(variant[0])) return false; else variants.add(variant[0]); } else return false; } if (variants.contains(FontVariant.NORMAL) && (variants.size() > 1)) { return false; // if includes normal, then cannot include another token } else if (variants.contains(FontVariant.SUPER) && variants.contains(FontVariant.SUB)) { return false; // cannot include both super and sub } else if (variants.contains(FontVariant.HALF) && variants.contains(FontVariant.FULL)) { return false; // cannot include both half and full } if (outputVariants != null) { outputVariants.clear(); outputVariants.addAll(variants); } return true; } public static void badFontVariants(String value, Location location, VerifierContext context) { Reporter reporter = context.getReporter(); Locator locator = location.getLocator(); Set<FontVariant> variants = new java.util.HashSet<FontVariant>(); String [] variantTokens = splitFontVariants(value); for (String token : variantTokens) { FontVariant[] variant = new FontVariant[1]; if (!isFontVariant(token, location, context, variant)) { badFontVariant(token, location, context); } else if (variants.contains(variant[0])) { reporter.logInfo(reporter.message(locator, "*KEY*", "Duplicate token of <font-variant> expression ''{0}''.", token.trim())); } else { variants.add(variant[0]); } } if (variants.contains(FontVariant.NORMAL) && (variants.size() > 1)) { reporter.logInfo(reporter.message(locator, "*KEY*", "Bad <font-variant> expression ''{0}'', no other token permitted when ''normal'' is present.", value)); } else if (variants.contains(FontVariant.SUPER) && variants.contains(FontVariant.SUB)) { reporter.logInfo(reporter.message(locator, "*KEY*", "Bad <font-variant> expression ''{0}'', cannot include both ''super'' and ''sub''.", value)); } else if (variants.contains(FontVariant.HALF) && variants.contains(FontVariant.FULL)) { reporter.logInfo(reporter.message(locator, "*KEY*", "Bad <font-variant> expression ''{0}'', cannot include both ''halfWidth'' and ''fullWidth''.", value)); } } private static String[] splitFontVariants(String value) { return value.split("\\s+"); } }