1 /* 2 * Copyright 2011 Google Inc. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.typography.font.sfntly.table; 18 19 import com.google.typography.font.sfntly.data.ReadableFontData; 20 import com.google.typography.font.sfntly.data.WritableFontData; 21 22 import java.io.IOException; 23 import java.io.OutputStream; 24 25 /** 26 * An abstract base for any table that contains a FontData. This is the root of 27 * the table class hierarchy. 28 * 29 * @author Stuart Gill 30 * 31 */ 32 public abstract class FontDataTable { 33 protected ReadableFontData data; 34 /** 35 * Constructor. 36 * 37 * @param data the data to back this table 38 */ FontDataTable(ReadableFontData data)39 FontDataTable(ReadableFontData data) { 40 this.data = data; 41 } 42 43 /** 44 * Gets the readable font data for this table. 45 * 46 * @return the read only data 47 */ readFontData()48 public ReadableFontData readFontData() { 49 return this.data; 50 } 51 52 @Override toString()53 public String toString() { 54 return this.data.toString(); 55 } 56 57 /** 58 * Gets the length of the data for this table in bytes. This is the full 59 * allocated length of the data underlying the table and may or may not 60 * include any padding. 61 * 62 * @return the data length in bytes 63 */ dataLength()64 public final int dataLength() { 65 return this.data.length(); 66 } 67 serialize(OutputStream os)68 public int serialize(OutputStream os) throws IOException { 69 return this.data.copyTo(os); 70 } 71 serialize(WritableFontData data)72 protected int serialize(WritableFontData data) { 73 return this.data.copyTo(data); 74 } 75 76 public static abstract class Builder<T extends FontDataTable> { 77 private WritableFontData wData; 78 private ReadableFontData rData; 79 private boolean modelChanged; 80 private boolean containedModelChanged; // may expand to list of submodel states 81 private boolean dataChanged; 82 83 /** 84 * Constructor. 85 * 86 * Construct a FontDataTable.Builder with a WritableFontData backing store 87 * of size given. A positive size will create a fixed size backing store and 88 * a 0 or less size is an estimate for a growable backing store with the 89 * estimate being the absolute of the size. 90 * 91 * @param dataSize if positive then a fixed size; if 0 or less then an 92 * estimate for a growable size 93 */ Builder(int dataSize)94 protected Builder(int dataSize) { 95 this.wData = WritableFontData.createWritableFontData(dataSize); 96 } 97 Builder(WritableFontData data)98 protected Builder(WritableFontData data) { 99 this.wData = data; 100 } 101 Builder(ReadableFontData data)102 protected Builder(ReadableFontData data) { 103 this.rData = data; 104 } 105 106 /** 107 * Gets a snapshot copy of the internal data of the builder. 108 * 109 * This causes any internal data structures to be serialized to a new data 110 * object. This data object belongs to the caller and must be properly 111 * disposed of. No changes are made to the builder and any changes to the 112 * data directly do not affect the internal state. To do that a subsequent 113 * call must be made to {@link #setData(WritableFontData)}. 114 * 115 * @return a copy of the internal data of the builder 116 * @see FontDataTable.Builder#setData(WritableFontData) 117 */ data()118 public WritableFontData data() { 119 WritableFontData newData; 120 if (this.modelChanged) { 121 if (!this.subReadyToSerialize()) { 122 throw new RuntimeException("Table not ready to build."); 123 } 124 int size = subDataSizeToSerialize(); 125 newData = WritableFontData.createWritableFontData(size); 126 this.subSerialize(newData); 127 } else { 128 ReadableFontData data = internalReadData(); 129 newData = WritableFontData.createWritableFontData(data != null ? data.length() : 0); 130 if (data != null) { 131 data.copyTo(newData); 132 } 133 } 134 return newData; 135 } 136 setData(WritableFontData data)137 public void setData(WritableFontData data) { 138 this.internalSetData(data, true); 139 } 140 141 /** 142 * @param data 143 */ setData(ReadableFontData data)144 public void setData(ReadableFontData data) { 145 this.internalSetData(data, true); 146 } 147 internalSetData(WritableFontData data, boolean dataChanged)148 private void internalSetData(WritableFontData data, boolean dataChanged) { 149 this.wData = data; 150 this.rData = null; 151 if (dataChanged) { 152 this.dataChanged = true; 153 this.subDataSet(); 154 } 155 } 156 internalSetData(ReadableFontData data, boolean dataChanged)157 private void internalSetData(ReadableFontData data, boolean dataChanged) { 158 this.rData = data; 159 this.wData = null; 160 if (dataChanged) { 161 this.dataChanged = true; 162 this.subDataSet(); 163 } 164 } 165 build()166 public T build() { 167 T table = null; 168 169 ReadableFontData data = this.internalReadData(); 170 if (this.modelChanged) { 171 // let subclass serialize from model 172 if (!this.subReadyToSerialize()) { 173 return null; 174 } 175 int size = subDataSizeToSerialize(); 176 WritableFontData newData = WritableFontData.createWritableFontData(size); 177 this.subSerialize(newData); 178 data = newData; 179 } 180 181 if (data != null) { 182 table = this.subBuildTable(data); 183 this.notifyPostTableBuild(table); 184 } 185 this.rData = null; 186 this.wData = null; 187 188 return table; 189 } 190 readyToBuild()191 public boolean readyToBuild() { 192 return true; 193 } 194 internalReadData()195 protected ReadableFontData internalReadData() { 196 if (this.rData != null) { 197 return this.rData; 198 } 199 return this.wData; 200 } 201 internalWriteData()202 protected WritableFontData internalWriteData() { 203 if (this.wData == null) { 204 WritableFontData newData = 205 WritableFontData.createWritableFontData(this.rData == null ? 0 : this.rData.length()); 206 if (this.rData != null) { 207 this.rData.copyTo(newData); 208 } 209 this.internalSetData(newData, false); 210 } 211 return this.wData; 212 } 213 214 /** 215 * Determines whether the state of this builder has changed - either the data or the internal 216 * model representing the data. 217 * 218 * @return true if the builder has changed 219 */ changed()220 public boolean changed() { 221 return this.dataChanged() || this.modelChanged(); 222 } 223 dataChanged()224 protected boolean dataChanged() { 225 return this.dataChanged; 226 } 227 modelChanged()228 protected boolean modelChanged() { 229 return this.currentModelChanged() || this.containedModelChanged(); 230 } 231 currentModelChanged()232 protected boolean currentModelChanged() { 233 return this.modelChanged; 234 } 235 containedModelChanged()236 protected boolean containedModelChanged() { 237 return this.containedModelChanged; 238 } 239 setModelChanged()240 protected boolean setModelChanged() { 241 return this.setModelChanged(true); 242 } 243 setModelChanged(boolean changed)244 protected boolean setModelChanged(boolean changed) { 245 boolean old = this.modelChanged; 246 this.modelChanged = changed; 247 return old; 248 } 249 250 // subclass API 251 252 /** 253 * Notification to subclasses that a table was built. 254 */ notifyPostTableBuild(T table)255 protected void notifyPostTableBuild(T table) { 256 // NOP - 257 } 258 259 /** 260 * Serialize the table to the data provided. 261 * 262 * @param newData the data object to serialize to 263 * @return the number of bytes written 264 */ subSerialize(WritableFontData newData)265 protected abstract int subSerialize(WritableFontData newData); 266 267 /** 268 * @return true if the subclass is ready to serialize it's structure into 269 * data 270 */ subReadyToSerialize()271 protected abstract boolean subReadyToSerialize(); 272 273 /** 274 * Query if the subclass needs to serialize and how much data is required. 275 * 276 * @return positive bytes needed to serialize if a fixed size; and zero or 277 * negative bytes as an estimate if growable data is needed 278 */ subDataSizeToSerialize()279 protected abstract int subDataSizeToSerialize(); 280 281 /** 282 * Tell the subclass that the data has been changed and any structures must 283 * be discarded. 284 */ subDataSet()285 protected abstract void subDataSet(); 286 287 /** 288 * Build a table with the data provided. 289 * 290 * @param data the data to use to build the table 291 * @return a table 292 */ subBuildTable(ReadableFontData data)293 protected abstract T subBuildTable(ReadableFontData data); 294 } 295 } 296