• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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